package com.cory.module.job.core.handle;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import cn.afterturn.easypoi.handler.inter.IExcelDictHandler;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.cory.boot.domain.CustomRuntimeException;
import com.cory.utils.lang.ObjectUtils;
import com.cory.utils.lang.StringUtils;
import com.cory.utils.web.WebUtils;
import com.google.common.base.Joiner;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Autowired;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import cn.afterturn.easypoi.handler.inter.IExcelVerifyHandler;
import cn.afterturn.easypoi.handler.inter.IReadHandler;
import com.cory.boot.component.easypoi.EasyExcelUtils;
import com.cory.boot.component.easypoi.IExcelDictHandlerImpl;
import com.cory.boot.component.uploader.UploaderUtils;
import com.cory.boot.utils.SpringUtils;
import com.cory.module.job.core.annotation.ExcelImportJob;
import com.cory.module.job.domain.entity.JobExcelImport;
import com.cory.module.job.domain.entity.JobExcelImportDetail;
import com.cory.module.job.domain.vo.ExcelImportModel;
import com.cory.module.job.service.JobExcelImportDetailService;
import com.cory.module.job.service.JobExcelImportService;
import com.cory.utils.bean.ReflectioUtils;
import com.google.common.collect.Lists;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

@Slf4j
public abstract class ExcelImportJobHandle<T extends ExcelImportModel> {

    @Autowired
    private JobExcelImportService jobExcelImportService;
    @Autowired
    private JobExcelImportDetailService jobExcelImportDetailService;
    @Autowired
    private Validator validator;

    public void run(JobExcelImport job) {
        ImportParams params = new ImportParams();
        params.setNeedVerify(true);
        params.setDictHandler(this.excelDictHandler());
        params.setVerifyHandler(this.excelVerifyHandler());

        try {
            this.doBefore(job);
            ExcelImportJob annotation = this.getClass().getAnnotation(ExcelImportJob.class);
            if (annotation.importMode() == 1) {
                this.single(job, params);
            }
            if (annotation.importMode() == 2) {
                this.batch(job, params);
            }
            this.doAfter(job);
        } catch (Exception e) {
            log.error("", e);
            JobExcelImport update = new JobExcelImport().setId(job.getId()).setState(2);
            jobExcelImportService.updateById(update);
        }

    }

    /**
     * 一次性导入
     */
    protected void single(JobExcelImport job, ImportParams params) {
        ExcelImportResult<T> result = ExcelImportUtil.importExcelMore(UploaderUtils.getFile(job.getFileId()), tClass(), params);

        List<T> errors = result.getFailList();
        int okNum = 0;
        for (T t : result.getList()) {
            if (this.handle(job, t)) {
                okNum++;
            } else if (StringUtils.isNotBlank(t.getErrorMsg())) {
                errors.add(t);
            }
        }

        JobExcelImport update = new JobExcelImport().setId(job.getId()).setState(1).setOkNum(okNum).setErrorNum(errors.size());
        jobExcelImportService.updateById(update);
        this.saveErrors(job.getId(), errors);
    }

    /**
     * 分批次导入
     */
    protected void batch(JobExcelImport job, ImportParams params) throws Exception {
        ExcelImportUtil.importExcelBySax(new FileInputStream(UploaderUtils.getFile(job.getFileId())), tClass(), params, new ExcelImportBatchHandle<T>(this, job));

        JobExcelImport update = new JobExcelImport().setId(job.getId()).setState(1);
        jobExcelImportService.updateById(update);
    }

    protected boolean handle(JobExcelImport job, T t) {
        // 空数据
        if (ObjectUtils.getNotEmptyPropertyNames(t, "rowNum", "errorMsg").length == 0) {
            return false;
        }

        Set<ConstraintViolation<T>> result = validator.validate(t);
        if (result.size() > 0) {
            t.setErrorMsg(Joiner.on(";").join(result.stream().map(i -> i.getMessage()).collect(Collectors.toList())));
            return false;
        }

        try {
            return this.doHandle(t);
        } catch (CustomRuntimeException e) {
            t.setErrorMsg(e.getMessage());
            return false;
        } catch (Exception e) {
            t.setErrorMsg("导入错误");
            return false;
        }
    }

    protected abstract boolean doHandle(T t);

    protected IExcelVerifyHandler<T> excelVerifyHandler() {
        return null;
    }

    protected IExcelDictHandler excelDictHandler() {
        return new IExcelDictHandlerImpl();
    }

    @SneakyThrows
    @SuppressWarnings("unchecked")
    public void tpl(HttpServletRequest request, HttpServletResponse response) {
        ExcelImportJob annotation = this.getClass().getAnnotation(ExcelImportJob.class);
        if (annotation.tplMode() == 2) {
            Resource resource = new ClassPathResource(String.format("file/import/%s.xlsx", annotation.type()));
            File file = resource.getFile();
            WebUtils.writeFile(response, file, annotation.name() + ".xlsx");
            return;
        }

        ExportParams params = new ExportParams();
        params.setDictHandler(this.excelDictHandler());
        Workbook workbook = ExcelExportUtil.exportExcel(params, tClass(), tplData());
        EasyExcelUtils.downLoadExcel(response, workbook, annotation.name() + ".xlsx");
    }

    public void exportError(HttpServletRequest request, HttpServletResponse response, Integer jobId) {
        ExcelImportJob annotation = this.getClass().getAnnotation(ExcelImportJob.class);
        Class<T> T = tClass();

        List<T> list = Lists.newArrayList();
        List<JobExcelImportDetail> errors = jobExcelImportDetailService.errorList(jobId);
        for (JobExcelImportDetail error : errors) {
            T t = JSONObject.parseObject(error.getData(), T);
            t.setErrorMsg(error.getErrorMsg());
            list.add(t);
        }

        ExportParams params = new ExportParams();
        params.setDictHandler(this.excelDictHandler());
        Workbook workbook = ExcelExportUtil.exportExcel(params, T, list);
        EasyExcelUtils.downLoadExcel(response, workbook, annotation.name() + "_错误数据" + ".xlsx");
    }

    public List<T> tplData() {
        return new ArrayList<T>();
    }

    public void doBefore(JobExcelImport job) {}

    public void doAfter(JobExcelImport job) {}

    private void saveErrors(Integer jobId, List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        list.sort(Comparator.comparingInt(i -> i.getRowNum()));
        List<JobExcelImportDetail> details = list.stream().map(i -> new JobExcelImportDetail().error(jobId, i)).collect(Collectors.toList());
        jobExcelImportDetailService.saveBatch(details);
    }

    @SuppressWarnings("unchecked")
    private Class<T> tClass() {
        return ReflectioUtils.getClassGenricType(this.getClass(), 0);
    }

    protected static class ExcelImportBatchHandle<T extends ExcelImportModel> implements IReadHandler<T> {
        int okNum = 0, errorNum = 0, row = 1, count = 0;
        List<T> errors = Lists.newArrayList();

        ExcelImportJobHandle<T> handle;
        JobExcelImport job;

        public ExcelImportBatchHandle(ExcelImportJobHandle<T> handle, JobExcelImport job) {
            this.handle = handle;
            this.job = job;
        }

        @Override
        public void handler(T t) {
            row++;
            this.progress();

            if (handle.handle(job, t)) {
                okNum++;
            } else if (StringUtils.isNotBlank(t.getErrorMsg())) {
                errorNum++;
                errors.add(t);
            }
        }

        public void progress() {
            count++;
            if (count == 1000) {
                JobExcelImport update = new JobExcelImport().setId(job.getId()).setOkNum(okNum).setErrorNum(errorNum);
                SpringUtils.getBean(JobExcelImportService.class).updateById(update);
                handle.saveErrors(job.getId(), this.errors);

                this.errors.clear();
                count = 0;
            }
        }

        @Override
        public void doAfterAll() {}
    }
}
